package com.roshan.dao.impl;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.log4j.Logger;
import org.hibernate.Criteria;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;

import com.roshan.dao.BaseDAO;
import com.roshan.domain.BaseDomain;
import com.roshan.exception.ObjectNotFoundException;
import com.roshan.strategy.UriGenerationStrategy;

@SuppressWarnings( {"unchecked", "nls"})
public abstract class BaseDAOImpl<T extends BaseDomain, S extends Serializable> implements BaseDAO<T, S> {

    protected static Logger logger = Logger.getLogger(BaseDAOImpl.class);

    @Autowired
    private SessionFactory sessionFactory;

    @Autowired
    protected UriGenerationStrategy uriGenerator;

    @Override
    public T save(T entity) {
        Date now = new Date();
        entity.setCreateDate(now);
        entity.setLastModifiedDate(now);
        entity.setUri(this.uriGenerator.generateURI(entity));
        this.setCascadeEntityUri(entity);
        this.setCascadeEntityCreateDate(entity);
        S s = (S) getSession().save(entity);
        entity.setId(s);
        return entity;
    }

    @Override
    public void delete(T entity) {
        getSession().delete(entity);
    }

    @Override
    public void delete(S id) {
        T t = findById(id);
        if (t == null) {
            throw new ObjectNotFoundException("Object not found with id : " + id);
        }
        delete(t);
    }

    @Override
    public boolean exists(S id) {
        return !(findById(id) == null);
    }

    @Override
    public void update(T entity) {
        entity.setLastModifiedDate(new Date());
        this.setCascadeEntityUri(entity);
        this.setCascadeEntityCreateDate(entity);
        getSession().update(entity);
    }

    @Override
    public T findById(S id) {

        T t = (T) getSession().get(getGenericClass(), id);
        return t;

    }

    @Override
    public void flush() {
        getSession().flush();
    }

    public Session getSession() {
        return this.sessionFactory.getCurrentSession();
    }

    @Override
    public List<T> findAll() {
        return findAll(-1, -1);
    }

    @Override
    public List<T> findAllDistinct() {
        Collection result = new LinkedHashSet(findAll());
        return new ArrayList(result);
    }

    @Override
    public List<T> findByNamedQuery(String queryName, Map<String, Object> queryParams) {

        String[] params = new String[queryParams.size()];
        Object[] values = new Object[queryParams.size()];

        int index = 0;
        for (String s : queryParams.keySet()) {
            params[index] = s;
            values[index++] = queryParams.get(s);
        }

        Query namedQuery = getSession().getNamedQuery(queryName);

        for (Map.Entry<String, Object> entry : queryParams.entrySet()) {

            Object value = entry.getValue();
            if (value != null) {

                if (value instanceof String) {
                    namedQuery.setString(entry.getKey(), (String) value);
                } else if (value instanceof Boolean) {
                    namedQuery.setBoolean(entry.getKey(), ((Boolean) value).booleanValue());
                } else if (value instanceof Date) {
                    namedQuery.setDate(entry.getKey(), (Date) value);
                } else if (value instanceof Integer) {
                    namedQuery.setInteger(entry.getKey(), ((Integer) value).intValue());
                } else {
                    throw new RuntimeException("System does not support this object type for named query.");
                }

            }

        }

        return namedQuery.list();
    }

    protected List<T> findAll(int first, int max) {

        Class<T> clazz = getGenericClass();
        Criteria criteria = getSession().createCriteria(clazz);

        if (first > -1) {
            criteria.setFirstResult(first);
            criteria.setMaxResults(max);
        }

        return criteria.list();
    }

    protected Object setCascadeEntityUri(Object entity) {
        Field[] fields = entity.getClass().getDeclaredFields();

        String className = null;
        for (int i = 0; i < fields.length; i++) {
            try {
                fields[i].setAccessible(true);

                if (fields[i].get(entity) instanceof BaseDomain<?>) {
                    className = entity.getClass().getSimpleName() + "."
                            + fields[i].get(entity).getClass().getSimpleName();

                    BaseDomain<Long> domainEntity = (BaseDomain<Long>) fields[i].get(entity);

                    processUri(domainEntity);

                } else if (fields[i].get(entity) instanceof List<?> && !(((List) fields[i].get(entity)).isEmpty())
                        && (((List) fields[i].get(entity)).iterator().next() instanceof BaseDomain<?>)) {

                    List<BaseDomain<Long>> entities = (List<BaseDomain<Long>>) fields[i].get(entity);
                    for (BaseDomain<Long> domainEntity : entities) {

                        processUri(domainEntity);
                    }

                } else if (fields[i].get(entity) instanceof Set<?> && !(((Set) fields[i].get(entity)).isEmpty())
                        && (((Set) fields[i].get(entity)).iterator().next() instanceof BaseDomain<?>)) {

                    Set<BaseDomain<Long>> entities = (Set<BaseDomain<Long>>) fields[i].get(entity);

                    for (BaseDomain<Long> domainEntity : entities) {

                        processUri(domainEntity);
                    }
                }
            } catch (IllegalArgumentException e) {
                throw new RuntimeException(e.getMessage() + " " + className, e);
            } catch (IllegalAccessException e) {
                throw new RuntimeException(e.getMessage() + " " + className, e);
            } catch (SecurityException e) {
                throw new RuntimeException(e.getMessage() + " " + className, e);
            } catch (NoSuchFieldException e) {
                throw new RuntimeException(e.getMessage() + " " + className, e);
            }
        }
        return entity;
    }

    protected void processUri(BaseDomain<Long> domainEntity) throws NoSuchFieldException, SecurityException,
            IllegalArgumentException, IllegalAccessException {
        Field uriField = this.findSuperClass(domainEntity.getClass()).getDeclaredField("uri");
        uriField.setAccessible(true);

        if (uriField.get(domainEntity) == null) {
            domainEntity.setUri(this.uriGenerator.generateURI(domainEntity));
            setCascadeEntityUri(domainEntity);
        }
    }

    protected Object setCascadeEntityCreateDate(Object entity) {
        Field[] fields = entity.getClass().getDeclaredFields();

        String className = null;
        for (int i = 0; i < fields.length; i++) {
            try {
                fields[i].setAccessible(true);

                if (fields[i].get(entity) instanceof BaseDomain<?>) {
                    className = entity.getClass().getSimpleName() + "."
                            + fields[i].get(entity).getClass().getSimpleName();

                    BaseDomain<Long> domainEntity = (BaseDomain<Long>) fields[i].get(entity);

                    processCreateDate(domainEntity);
                } else if (fields[i].get(entity) instanceof List<?> && !(((List) fields[i].get(entity)).isEmpty())
                        && (((List) fields[i].get(entity)).iterator().next() instanceof BaseDomain<?>)) {

                    List<BaseDomain<Long>> entities = (List<BaseDomain<Long>>) fields[i].get(entity);
                    for (BaseDomain<Long> domainEntity : entities) {
                        processCreateDate(domainEntity);
                    }

                } else if (fields[i].get(entity) instanceof Set<?> && !(((Set) fields[i].get(entity)).isEmpty())
                        && (((Set) fields[i].get(entity)).iterator().next() instanceof BaseDomain<?>)) {

                    Set<BaseDomain<Long>> entities = (Set<BaseDomain<Long>>) fields[i].get(entity);
                    for (BaseDomain<Long> domainEntity : entities) {
                        processCreateDate(domainEntity);
                    }
                }
            } catch (IllegalArgumentException e) {
                throw new RuntimeException(e.getMessage() + " " + className, e);
            } catch (IllegalAccessException e) {
                throw new RuntimeException(e.getMessage() + " " + className, e);
            } catch (SecurityException e) {
                throw new RuntimeException(e.getMessage() + " " + className, e);
            } catch (NoSuchFieldException e) {
                throw new RuntimeException(e.getMessage() + " " + className, e);
            }
        }
        return entity;
    }

    protected void processCreateDate(BaseDomain<Long> domainEntity) throws NoSuchFieldException, SecurityException,
            IllegalArgumentException, IllegalAccessException {
        Field createDateField = this.findSuperClass(domainEntity.getClass()).getDeclaredField("createDate");

        createDateField.setAccessible(true);

        if (createDateField.get(domainEntity) == null) {
            domainEntity.setCreateDate(new Date());
            setCascadeEntityCreateDate(domainEntity);
        }
    }

    protected Class findSuperClass(Class clazz) {
        if (clazz.getSuperclass() != null && !clazz.getSuperclass().equals(Object.class)) {
            return findSuperClass(clazz.getSuperclass());
        }
        return clazz;
    }

    protected Class<T> getGenericClass() {
        return (Class<T>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
    }

    public SessionFactory getSessionFactory() {
        return this.sessionFactory;
    }
}
